#include "my_gets_and_my_puts.h"
#include "check.h"

//一次性读取len个字符，返回值为len                                                                           
int recvn(int netfd,void * buf, int len) {
    char *p = (char*)buf;
    int cur = 0;
    while (cur < len) {
        int ret = recv(netfd, p + cur, len - cur, 0);
        ERRNO_LOGGING(ret, -1, "recv failed");
        //服务端关闭了,服务端直接退出
        if (ret == 0) {
            INFO_LOGGING("recvn return -1");
            return -1;
        }
        cur += ret;
    }
    return len;
}
//小火车装车，返回值检错
int padding_train_by_buf(Train_t * train, void * buf,  DataType_t type, int len) {
    train->data_type = type;
    train->data_len = len;
    memcpy(train->data, buf, len);
    return 0;
}
//接收小火车
int recv_train(int fd, Train_t * dest) {
    //接收type和len
    int type_and_len_len = sizeof(dest->data_type) + sizeof(dest->data_len);
    int ret = recvn(fd, &dest->data_type,type_and_len_len);
    if (ret == -1) {
        INFO_LOGGING("recv_train return -1");
        return -1;
    }
    //接收data
    ret = recvn(fd, dest->data,dest->data_len);
    if (ret == -1) {
        INFO_LOGGING("recv_train return -1");
        return -1;
    }

    return 0;
}
//发送小火车,返回值为send的返回值，用于判断客户端是否断开
int send_train(int netfd, Train_t * train) {
    int len = sizeof(train->data_len) + sizeof(train->data_type) + train->data_len;
    int ret = send(netfd, train, len, 0);
    return ret;
}
//大文件发送
int send_big_file(int netfd,int src_fd, off_t len, off_t offset) {
    char * buf = (char*)mmap(NULL, len + offset, PROT_READ | PROT_WRITE, MAP_SHARED, src_fd, 0);
    ERRNO_LOGGING(buf, MAP_FAILED, "mmap failed");
    int ret = send(netfd, buf + offset, len, MSG_NOSIGNAL);
    WARNING_LOGGING(ret, -1, "a client is die in getting");
    munmap(buf, len + offset);
    close(src_fd);
    return 0;
}
//小文件发送
int send_little_file(int netfd,int src_fd, off_t len) {
    off_t cur = 0;
    char buf[4096];
    while (cur < len) {
        memset(buf, 0, sizeof(buf));
        int ret = read(src_fd, buf, sizeof(buf));
        send(netfd, buf, ret, MSG_NOSIGNAL);
        cur += ret;
    }
    close(src_fd);
    return 0;
}

//大文件接收
int recv_and_gets_big_file(int dest_fd, int src_fd, off_t len,off_t offset) {
    ftruncate(dest_fd, len + offset);
    char * buf = (char*)mmap(NULL, len + offset, PROT_READ | PROT_WRITE, MAP_SHARED, dest_fd, 0);
    ERRNO_LOGGING(buf, MAP_FAILED, "mmap failed");
    int ret = recvn(src_fd, buf + offset, len);
    if (ret == -1) {
        INFO_LOGGING("recv_and_gets_big_file return -1");
        return -1;
    }
    munmap(buf, len + offset);
    close(dest_fd);
    return 0;

}
//小文件接收
int recv_and_gets_little_file(int dest_fd, int src_fd, off_t len) {
    off_t cur = 0;
    char buf[4096];
    while (cur < len) {
        memset(buf, 0, sizeof(buf));
        int ret = recv(src_fd, buf, sizeof(buf), 0);
        if (ret == 0) {
            INFO_LOGGING("recv_and_gets_little_file return -1");
            return -1;
        }
        write(dest_fd, buf, ret);
        cur += ret;
    }
    close(dest_fd);
    return 0;
}

//ls
int my_ls(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//cd
int my_cd(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//mkdir
int my_mkdir(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//rmdir
int my_rmdir(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//remove
int my_remove(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//pwd
int my_pwd(Train_t train) {
    printf("%s\n", train.data);
    return 0;
}
//gets下载客户端
int my_gets(int fd, Train_t * train) {
    //通过type判断，打印命令终止信息
    if (train->data_type == GETS_FAILED) {//服务器不存在该文件
        printf("%s\n", train->data);
        return 0;
    }
    //服务器存在该文件
    //保存dest文件路径
    char dest_file_name[100] = {0};
    memcpy(dest_file_name, train->data, train->data_len);
    //用access函数判断是否存在同名文件
    int is_exist = access(dest_file_name, F_OK);
    if (is_exist == 0) {//存在同名文件
                        //以追加模式打开文件
        int file_fd = open(dest_file_name, O_RDWR | O_APPEND);
        //用fstat查看文件大小
        struct stat state;
        fstat(file_fd, &state);
        off_t size = state.st_size;

        char md5_val[33] = {0};
        //计算MD5
        get_md5(dest_file_name, md5_val, size);
        padding_train_by_buf(train, md5_val, SAME_NAME_FILE, strlen(md5_val) + 1);
        send_train(fd, train);
        //将文件大小发送到服务端
        padding_train_by_buf(train, &size, SAME_NAME_FILE, sizeof(size));
        send_train(fd, train);
        //接收服务端小火车，用type区分是重名还是断点继续下载
        int sret = recv_train(fd,train);
        if (sret == -1) {
            printf("The server disconnects\n");
            INFO_LOGGING("The server disconnects");
            return -1;
        }
        if (train->data_type == GETS_FAILED) {//重名
            printf("%s\n", train->data);
            return 0;
        }
        //是断点继续下载
        //接收文件大小

        off_t len = *(off_t*)train->data;
        if (size > BIG_FILE) {
            sret = recv_and_gets_big_file(file_fd, fd, len, size);
            if (sret == -1) {
                printf("The server disconnects\n");
                INFO_LOGGING("The server disconnects");
                return -1;
            }
        }else {
            sret = recv_and_gets_little_file(file_fd, fd, len);
            if (sret == -1) {
                printf("The server disconnects\n");
                INFO_LOGGING("The server disconnects");
                return -1;
            }
        }
    }else {//不存在同名文件
           //打开文件
        int dest_fd = open(dest_file_name, O_RDWR | O_CREAT | O_TRUNC, 0666);
        //发送一个开始传输文件请求
        char buf[] = "start";
        padding_train_by_buf(train, buf, NOT_SAME_NAME, sizeof(buf));
        send_train(fd, train);
        //接收文件大小
        int sret = recv_train(fd, train);
        if (sret == -1) {
            printf("The server disconnects\n");
            INFO_LOGGING("The server disconnects");
            return -1;
        }
        off_t size = *(off_t*)train->data;
        if (size > BIG_FILE) {
            sret = recv_and_gets_big_file(dest_fd, fd, size, 0);
            if (sret == -1) {
                printf("The server disconnects\n");
                INFO_LOGGING("The server disconnects");
                return -1;
            }
        }else {
            sret = recv_and_gets_little_file(dest_fd, fd, size);
            if (sret == -1) {
                printf("The server disconnects\n");
                INFO_LOGGING("The server disconnects");
                return -1;
            }

        }
    }
    return 0;
}
//puts上传客户端
int my_puts(int netfd, Train_t * train) {
    //收到puts错误信息直接打印
    //这是收到的第一个火车
    if (train->data_type != COMMAND_PUTS) {
        printf("%s\n", train->data);
        return 0;
    }
    char client_path[100] = {0};
    memcpy(client_path, train->data, train->data_len);
    //判断文件是否存在，用access函数
    int is_exist = access(client_path, F_OK);
    if (is_exist != 0){//文件不存在
                       //发送错误处理的小火车
                       //这是发送的第一个火车
        char errbuf[] = "puts: 没有这个文件";
        padding_train_by_buf(train, errbuf, PUTS_FAILED,sizeof(errbuf));
        send_train(netfd, train);
        WARNING_LOGGING(1, 1, "puts: 客户端不存在这个文件");
        printf("puts: 没有这个文件\n");
        return 0;
    }
    //存在文件
    //用路径打开文件
    int src_file_fd = open(client_path, O_RDWR);
    ERRNO_LOGGING(src_file_fd, -1, "open file failed");
    //用fstat查看文件大小
    struct stat state;
    fstat(src_file_fd, &state);
    off_t size = state.st_size;
    //计算md5值
    char client_md5_val[33] = {0};
    get_md5(client_path, client_md5_val, size);

    //发送文件大小到服务端
    //这是发送的第2个火车
    padding_train_by_buf(train, &size, COMMAND_PUTS, sizeof(size));
    send_train(netfd, train);
    //发送md5到服务端
    //这是发送的第3个火车
    padding_train_by_buf(train, client_md5_val, COMMAND_PUTS, sizeof(client_md5_val));
    send_train(netfd, train);

    //接收第二个火车
    int sret = recv_train(netfd, train);
    if (sret == -1) {
        INFO_LOGGING("server is die");
        return 0;
    }
    //这个火车可能是PUTS_FAILED,PUTS_SECOND,COMMAND_PUTS
    //PUTS_FAILED代表是重名文件
    //直接打印
    if (train->data_type == PUTS_FAILED) {
        INFO_LOGGING(train->data);
        printf("%s\n", train->data);
    }else if (train->data_type == PUTS_SECOND) {
        //PUTS_SECOND代表是秒传
        //打日志

        INFO_LOGGING("进行了一次秒传");
    }else if (train->data_type == COMMAND_PUTS) {
        //COMMAND_PUTS代表是断点重传或者普通传
        //传输文件
        //接收偏移量
        off_t offset = *(off_t*)train->data;
        //偏移文件
        lseek(src_file_fd, offset, SEEK_SET);
        //计算传输大小
        off_t len = size - offset;
        //判断进行大文件传输还是小文件传输
        if (len > BIG_FILE) {
            //大文件发送
            send_big_file(netfd, src_file_fd, len, offset);
        }else {
            //小文件发送
            send_little_file(netfd, src_file_fd, len);
        }
    }else {
        //接收了一个脏包
        printf("服务端的数据包有误\n");
        ERRNO_LOGGING(1,1,"服务端的数据包有误");
    }


    return 0;
}


